<?php
declare (strict_types=1);
/**
 * +----------------------------------------------------------------------
 * | 功能扩展插件安装类3.1.0
 * | 原点 2024年1月5日09:15:50
 * | 依赖 ZipArchive扩展，目前仅支持本地环境安装，服务器环境暂不开放权限
 * +----------------------------------------------------------------------
 */

namespace addons;

use app\common\model\Menu;
use app\common\tool\Result;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use think\Exception;
use think\facade\Db;
use think\facade\View;
use think\Response;

class Plugin
{
    private string $api = 'http://erp.test.daenmende.com/';

    private string $apiUrl;

    private string $apiInfoUrl;

    private string $downloadUrl;

    private string $installedPath;

    private string $resourcePath;

    private string $slash = DIRECTORY_SEPARATOR;

    public function __construct()
    {
        $this->installedPath = app()->getRootPath() . 'addons' . $this->slash;
        $this->resourcePath = app()->getRootPath() . 'public' . $this->slash . 'static' . $this->slash . 'admin' . $this->slash;
        $this->apiUrl = $this->api . 'index/addons/index';
        $this->apiInfoUrl = $this->api . 'index/addons/info';
        $this->downloadUrl = $this->api . 'v82_addons/';
    }

    public function index()
    {
        return View::fetch(app()->getRootPath() . 'addons/index.html');
    }

    public function getList(): Response
    {
        try {
            $param = request()->get();
            //读取已经安装的插件
            $installed = $this->getInstalled();
            //读取线上插件
            $addons = $this->getPlugin('', isset($param['type']) ? $param['type'] : '');
            //匹配安装版本
            foreach ($addons['data'] as $k => &$v) {
                if (isset($installed[$v['key']])) {
                    $v['installedVersion'] = $installed[$v['key']]['version'];
                    if ($v['version'] != $installed[$v['key']]['version']) {
                        $v['install'] = 2;
                    } else {
                        $v['install'] = 1;
                    }
                    $v['locking'] = !empty($installed[$v['key']]['locking']) ? $installed[$v['key']]['locking'] : 0;
                } else {
                    $v['install'] = 0;
                    $v['installedVersion'] = '';
                }
            }
            return Result::fastReturn(['code' => 0, 'count' => 1, 'data' => $addons['data']]);
        } catch (\Exception $e) {
            return Result::fail($e->getMessage());
        }

    }

    public function install($preAddons = ''): Response|bool
    {
        try {
            if (!$preAddons) {
                if (!env('APP_DEBUG')) {
                    return Result::fail('非Debug环境，无法操作');
                }
                //拉取远程插件版本
                $addons = $this->getPlugin(request()->post('id'));
            } else {
                $addons = $preAddons;
            }
            //定义插件明细
            $name = $addons['key'];
            $format = $addons['format'];
            $completeName = $name . $format;
            $filename = $this->installedPath . $completeName;
            //下载插件
            if (!$this->download($filename, $addons['link'])) {
                return Result::fail('插件下载失败');
            }
            //解压插件
            $zip = new \ZipArchive;
            if ($zip->open($filename) === true) {
                //将压缩包文件解压到当前目录下
                $zip->extractTo($this->installedPath . $name);
                // 关闭zip文件
                $zip->close();
            } else {
                return Result::fail('文件解压失败，请稍后再试');
            }
            //删除旧包
            unlink($filename);

            //更新本地插件
            $this->setInstalled($name, $addons, false, $addons['isAdmin']);

            if ($addons['type'] == 1) {
                //读取是否有config
                if ($name != 'Builder') {
                    $this->copyConfig($name);
                }
                //读取是有有静态资源
                $resource = ['js', 'css', 'img'];
                foreach ($resource as $str) {
                    if (empty($addons['resource_path'])) continue;
                    $form = $this->installedPath . $name . '/resource/' . $str . $this->slash;
                    $to = app()->getRootPath() . $addons['resource_path'] . $str . $this->slash;
                    if (file_exists($form)) {
                        $this->copyDirs($form, $to);
                    }
                }
            } else {
                //读取是否有config
                $this->copyConfig($name);
                if ($addons['isAdmin'] == 1) {
                    $this->copyOther($name);
                } else if ($addons['isAdmin'] == 2) {
                    //读取admin
                    $this->copyAdmin($name, $addons['isAdmin']);
                } else {
                    //读取index
                    $this->copyIndex($name);
                    //读取静态资源
                    $resource = ['js', 'css', 'img'];
                    foreach ($resource as $str) {
                        $form = $this->installedPath . $name . $this->slash . 'resource' . $this->slash . 'index' . $this->slash . $str . $this->slash;
                        $to = app()->getRootPath() . 'public' . $this->slash . 'static' . $this->slash . 'index' . $this->slash . $str . $this->slash;
                        if (file_exists($form)) {
                            //删除旧的
                            $this->deleteDir($to);
                            //覆盖新的
                            $this->copyDirs($form, $to);
                        }
                    }
                }

                if ($addons['isAdmin'] > 1) {
                    //删除目录
                    $this->deleteDir($this->installedPath . $addons['key'] . $this->slash);
                }
            }
            return $preAddons ? true : Result::success('安装成功');
        } catch (\Exception $e) {
            return $preAddons ? throw new Exception($e->getMessage()) : Result::fail($e->getMessage());

        }
    }

    public function update(): Response
    {
        try {
            if (!env('APP_DEBUG')) {
                return Result::fail('非Debug环境，无法操作');
            }
            //拉取远程插件版本
            $addons = $this->getPlugin(request()->post('id'));

            //卸载插件
            $this->uninstall($addons);
            //重新安装
            $this->install($addons);

            return Result::success('更新成功');
        } catch (\Exception $e) {
            return Result::fail($e->getMessage());
        }
    }

    public function copyConfig($name)
    {
        $form = $this->installedPath . $name . $this->slash . 'config' . $this->slash;
        if (file_exists($form)) {
            $this->copyDirs($form, app()->getRootPath() . 'config' . $this->slash);
        }
    }

    public function copyIndex($name)
    {
        $form = $this->installedPath . $name . '/index/view/';
        if (file_exists($form)) {
            $to = app()->getBasePath() . '/index/view/';
            //删除旧view层
            $this->deleteDir($to);
            //重新覆盖view层
            $this->copyDirs($form, $to);
        }
    }

    public function getPath($name, $type, $isAdmin = 1)
    {
        $adminPath = $isAdmin == 2 ? $this->slash . 'admin' . $this->slash : $this->slash;
        return match ($type) {
            'form_js', 'form_css', 'form_images' => $this->installedPath . $name . $this->slash . 'resource' . $adminPath . explode('_', $type)[1] . $this->slash,
            'form_route' => $this->installedPath . $name . $adminPath . 'route' . $this->slash,
            'form_sql' => $this->installedPath . $name . $adminPath . 'install.sql',
            'form_config_file' => $this->installedPath . $name . $adminPath . 'config.php',
            'route' => app()->getAppPath() . 'route' . $this->slash,
            'js', 'css', 'images' => $this->resourcePath . 'addons' . $this->slash . strtolower($name) . $this->slash . $type . $this->slash,
            default => ''
        };
    }

    public function copyOther($name)
    {
        $dir = ['route', 'config.php', 'install.sql'];
        foreach ($dir as $key => $value) {
            switch ($value) {
                case "route":
                    $form = $this->installedPath . $name . '/' . $value . '/';
                    $to = app()->getAppPath() . $value . '/';

                    if (file_exists($form)) $this->copyDirs($form, $to);
                    break;
                case "config.php":
                    //创建菜单
                    $path = $this->getPath($name, 'form_config_file');
                    $config = file_exists($path) ? require $path : [];
                    if (isset($config['menu'])) {
                        foreach ($config['menu'] as $k => $v) {
                            $father = Menu::create($v);
                            if (isset($v['children'])) {
                                foreach ($v['children'] as $child) {
                                    $child['pid'] = $father->id;
                                    Menu::create($child);
                                }
                            }
                        }
                    }
                    break;
                case "install.sql":
                    //导入sql
                    $sqlFile = $this->getPath($name, 'form_sql');
                    if (is_file($sqlFile)) {
                        $lines = file($sqlFile);
                        $tempLine = '';
                        foreach ($lines as $line) {
                            if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') {
                                continue;
                            }
                            $tempLine .= $line;
                            if (substr(trim($line), -1, 1) == ';') {
                                $tempLine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $tempLine);
                                try {
                                    Db::execute($tempLine);
                                } catch (\Exception $e) {
                                    throw new Exception('Sql导入失败：' . $e->getMessage());
                                }
                                $tempLine = '';
                            }
                        }
                    }
                    break;

            }
        }
    }

    public function copyAdmin($name, $isAdmin)
    {
        //列出需要读取的目录
        $dir = ['validate', 'controller', 'tpl', 'validate', 'model', 'view', 'resource', 'route', 'config.php', 'install.sql'];
        foreach ($dir as $key => $value) {
            switch ($value) {
                case 'validate':
                case 'controller':
                case "route":
                case "tpl":
                case 'view':
                    $form = $this->installedPath . $name . '/admin/' . $value . '/';
                    $to = app()->getAppPath() . $value . '/';

                    if (file_exists($form)) $this->copyDirs($form, $to);
                    break;
                case 'model':
                    $form = $this->installedPath . $name . '/admin/model/';
                    $to = app()->getBasePath() . 'common/model/';
                    if (file_exists($form)) $this->copyDirs($form, $to);
                    break;

                case "resource":
                    $resource = ['js', 'css', 'images'];
                    foreach ($resource as $str) {
                        $form = $this->getPath($name, 'form_' . $str, $isAdmin);
                        if (file_exists($form)) {
                            $this->copyDirs($form, $this->getPath($name, $str));
                        }
                    }
                    break;
                case "config.php":
                    //创建菜单
                    $path = $this->getPath($name, 'form_config_file', $isAdmin);
                    $config = file_exists($path) ? require $path : [];
                    if (isset($config['menu'])) {
                        foreach ($config['menu'] as $k => $v) {
                            $father = Menu::create($v);
                            if (isset($v['children'])) {
                                foreach ($v['children'] as $child) {
                                    $child['pid'] = $father->id;
                                    Menu::create($child);
                                }
                            }
                        }
                    }
                    break;
                case "install.sql":
                    //导入sql
                    $sqlFile = $this->getPath($name, 'form_sql', $isAdmin);
                    if (is_file($sqlFile)) {
                        $lines = file($sqlFile);
                        $tempLine = '';
                        foreach ($lines as $line) {
                            if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') {
                                continue;
                            }
                            $tempLine .= $line;
                            if (substr(trim($line), -1, 1) == ';') {
                                $tempLine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $tempLine);
                                try {
                                    Db::execute($tempLine);
                                } catch (\Exception $e) {
                                    throw new Exception('Sql导入失败：' . $e->getMessage());
                                }
                                $tempLine = '';
                            }
                        }
                    }
                    break;

            }
        }
    }

    public function locking(): Response
    {
        try {
            //拉取远程插件版本
            $addons = $this->getPlugin(request()->post('id'));
            if (empty($addons)) return Result::fail('插件获取失败');
            $installed = $this->getInstalled();
            $installed[$addons['key']]['locking'] = 1;
            file_put_contents($this->installedPath . 'addons.json', json_encode($installed));
            return Result::success('已锁定');
        } catch (\Exception $e) {
            return Result::fail($e->getMessage());
        }
    }

    public function uninstall($preAddons = ''): Response|bool
    {
        try {
            if (!$preAddons) {
                if (!env('APP_DEBUG')) {
                    return Result::fail('非Debug环境，无法卸载');
                }
                //拉取远程插件版本
                $addons = $this->getPlugin(request()->post('id'));
            } else {
                $addons = $preAddons;
            }
            if ($addons['type'] == 2) {
                return Result::fail('集成插件无法卸载！');
            }

            //删除该插件Config
            $this->deleteConfig($addons['key']);
            //删除该插件相关目录所有文件
            $this->deleteDir($this->installedPath . $addons['key']);
            //删除插件静态资源
            if ($addons['resource_path']) {
                $this->deleteDir(app()->getRootPath() . $addons['resource_path']);
            }

            //更新本地插件列表
            $this->setInstalled($addons['key'], '', true);
            return $preAddons ? true : Result::success('卸载成功');
        } catch (\Exception $e) {
            return $preAddons ? throw new Exception($e->getMessage()) : Result::fail($e->getMessage());
        }
    }

    public function deleteConfig($name)
    {
        $path = $this->installedPath . $name . $this->slash . 'config' . $this->slash;
        if (file_exists($path)) {
            $files = scandir($path);
            if (is_array($files)) {
                $to = app()->getRootPath() . 'config' . $this->slash;
                foreach ($files as $v) {
                    if ($v != '.' && $v != '..') {
                        if (file_exists($to . $v)) {
                            unlink($to . $v);
                        }
                    }
                }
            }
        }
    }

    public function getPlugin($id = '', $type = '')
    {
        if ($id) {
            $plugin = file_get_contents($this->apiInfoUrl . '?v=v8&id=' . $id);
        } else {
            $plugin = file_get_contents($this->apiUrl . '?v=v8&type=' . $type);
        }
        if (!$plugin) {
            throw new Exception('读取插件失败，请稍后再试');
        }
        $data = json_decode($plugin, true);
        return $id ? $data['data'] : $data;

    }

    public function download($filename, $completeName)
    {
        return file_put_contents($filename, file_get_contents($this->downloadUrl . $completeName));
    }

    public function getInstalled()
    {
        if (!file_exists($this->installedPath . 'addons.json')) {
            throw new Exception('读取本地插件失败，请检查addons/addons.json');
        }
        return json_decode(file_get_contents($this->installedPath . 'addons.json'), true);
    }

    public function setInstalled($name, $addons = '', $del = false, $type = '')
    {
        $installed = $this->getInstalled();
        if ($del) {
            if (isset($installed[$name])) unset($installed[$name]);
        } else {

            if ($type == 3) {
                foreach ($installed as $item) {
                    if ($item['type'] == 3) {
                        unset($installed[$item['key']]);
                    }
                }
            }

            $installed[$name] = $addons;
            $installed[$name]['update_time'] = time();
        }
        return file_put_contents($this->installedPath . 'addons.json', json_encode($installed));
    }

    public function deleteDir($path)
    {
        if (is_dir($path)) {
            //扫描一个目录内的所有目录和文件并返回数组
            $dirs = scandir($path);
            foreach ($dirs as $dir) {
                //排除目录中的当前目录(.)和上一级目录(..)
                if ($dir != '.' && $dir != '..') {
                    //如果是目录则递归子目录，继续操作
                    $sonDir = $path . '/' . $dir;
                    if (is_dir($sonDir)) {
                        $this->deleteDir($sonDir);
                        @rmdir($sonDir);
                    } else {

                        @unlink($sonDir);
                    }
                }
            }
            @rmdir($path);
        }
    }

    public function copyDirs($source, $dest)
    {
        try {
            if (!is_dir($dest)) {
                mkdir($dest, 0755, true);
            }
            foreach ($iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
                RecursiveIteratorIterator::SELF_FIRST
            ) as $item) {
                if ($item->isDir()) {
                    $subDir = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
                    if (!is_dir($subDir)) {
                        mkdir($subDir, 0755, true);
                    }
                } else {
                    copy($item->getPathName(), $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
                }
            }
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }
}